/**
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

package pittypat.web;

import java.util.ArrayList;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.WebListener;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import pittypat.configuration.AppConfig;
import pittypat.configuration.PittypatConfig;
import pittypat.data.DbService;

/**
 * 在应用启动时加载相关资源，在应用终止时释放相关资源。
 * <p>
 * 应用启动时，ApiContextListener 负责从 <code>/WEB-INF/pittypat.xml</code>
 * 配置文件中加载相关的配置。如果没有提供配置或者配置文件无效，则应用无法正常启动或运行。
 * <p>
 * 数据库连接池是在运行过程中动态创建并缓存的，不是在应用启动时一次性创建的。
 * <p>
 * 当应用结束时，需要关闭所有已经打开的数据库连接池资源。
 */
@WebListener
public class ApiContextListener implements ServletContextListener {
	private static final Logger LOGGER = LogManager.getLogger(ApiContextListener.class);

	// 当前的 ServletContext
	private static ServletContext currentContext = null;
	// 同步对 currentContext 的访问
	private static final Object CURRENT_CONTEXT_LOCK = new Object();

	/**
	 * 获取当前的 ServletContext。
	 * <p>
	 * 当前的 ServletContext 在应用启动时被初始化，在此之前将保持为 null。
	 * 
	 * @return 一个 ServletContext 对象。
	 */
	public static ServletContext getCurrentContext() {
		synchronized (CURRENT_CONTEXT_LOCK) {
			return currentContext;
		}
	}

	/**
	 * 获取一个值，该值指示当前是否是调试版本。默认为 false。
	 * <p>
	 * 在 web.xml 中定义一个名称为 debug 的 context-param，值为 true 或 false。如果没有提供该配置参数，则默认为
	 * false。
	 * <p>
	 * 如果配置为正在调试应用程序，那么发送异常时，会向客户端发送详细的异常信息，包括堆栈跟踪信息。如果是发布版本，为了保证敏感信息不被泄露，以及提升性能，应该关闭调试选项。
	 * 
	 * @return 指示是否是调试版本，如果配置文件没有定义，默认为 false。
	 */
	public static boolean isDebug() {
		try {
			return Boolean.parseBoolean(getCurrentContext().getInitParameter("debug"));
		} catch (Exception e) {
			return false;
		}
	}

	/**
	 * 获取配置文件中定义的 App 虚拟根路径，以 '/' 开始，以 '/' 结束。默认为 "/"。
	 * <p>
	 * 在 web.xml 中定义一个名称为 appPath 的 context-param，值为路径字符串。如果没有提供该配置参数，则默认为 "/"。
	 * <p>
	 * Pittypat 约定请求路径的格式为 "{appPath}{开发者代码}/{应用标识符}/{模块标识符}/{功能标识符}" 。其中
	 * "{appPath}" 定义了虚拟路径的根，默认的该路径为 "/"，表示应用部署在站点的根目录下。
	 */
	public static String getAppPath() {
		String path = getCurrentContext().getInitParameter("appPath");

		// 如果没有提供该配置，或者长度为 0
		if (path == null || path.length() < 1) {
			return "/";
		}
		
		// 将 '\' 替换为 '/'
		path = path.replace('\\', '/');
		
		// 如果路径已经是 "/"
		if ("/".equals(path)) {
			return "/";
		}
		
		// 检查第一个字符是不是 '/'，如果不是，则在最前面添加 '/'
		if (path.charAt(0) != '/') {
			path = "/" + path;
		}

		// 检查最后一个字符是不是 '/'，如果不是，则在最后面添加 '/'
		if (path.charAt(path.length() - 1) != '/') {
			path = path + "/";
		}
		
		return path.toLowerCase();
	}

	/**
	 * 初始化 ApiContextListener 的新实例。
	 */
	public ApiContextListener() {
	}

	/**
	 * 应用启动时进行初始化工作，包括初始化应用上下文，读取并分析 <code>/WEB-INF/pittypat.xml</code> 配置文件。
	 */
	public void contextInitialized(ServletContextEvent sce) {
		// 保存当前 ServletContext
		synchronized (CURRENT_CONTEXT_LOCK) {
			currentContext = sce.getServletContext();
		}

		// 读取配置文件 pittypat.xml，分析所有配置信息
		try {
			PittypatConfig.init(currentContext.getRealPath("/WEB-INF/pittypat.xml"));
		} catch (Exception e) {
			LOGGER.fatal("无法加载 /WEB-INF/pittypat.xml 配置文件。", e);
		}

		// 添加 ApiServlet
		ServletRegistration reg = currentContext.addServlet(ApiServlet.class.getName(), new ApiServlet());
		
		// 添加路径映射
		ArrayList<String> developers = new ArrayList<String>();
		
		// 处理用户登录、注销等请求
		developers.add("user");
		// 处理通知消息的请求
		developers.add("notification");
		
		// 处理各个开发者代码下的路径
		String code = null;
		for (AppConfig app : PittypatConfig.getCurrent().getApps()) {
			code = app.getDeveloper().toLowerCase();
			if (!developers.contains(code)) {
				developers.add(code);
			}
		}
		
		for (String developer : developers) {
			reg.addMapping("/" + developer + "/*");
		}
	}

	/**
	 * 应用停止时清理相关的资源，包括关闭数据库连接池，将当前 CurrentContext 设置为 null。
	 */
	public void contextDestroyed(ServletContextEvent sce) {
		// 关闭所有的数据库连接池
		try {
			DbService.closeConnectionPools();
		} catch (Exception e) {
			LOGGER.error(e.getLocalizedMessage(), e);
		}
		
		// 重置当前 ServletContext 为 null
		synchronized (CURRENT_CONTEXT_LOCK) {
			currentContext = null;
		}
	}
}
